
########################################
##
## This module implements only the basic
## IRC operation needed by a basic Robot
##
##

import codecs
import irc_protocol
import re
import socket
import struct
import time

class Server:
    ##################################
    ##
    ## applies filters to keep sensitive
    ## information from being logged.
    ##
    ## each filter 'line' is composed of
    ## the following,
    ##
    ## (regexp, [field1,field2,...],[replacement1,replacement2,... ])
    ## 
    ##
    def filter(self, message):
        command = irc_protocol.parse(message)
        
        # the only part we want to filter, is
        # the message itself. from, cmd, and
        # to fields are to remain untouched.

        message_part=command[3]

        if message_part is not '':
            for filt in self.log_filters:
                r=filt[0].split(message_part)
                if len(r)>1:
                    for i in range(0,len(filt[1])):
                        if r[filt[1][i]] is not '':
                            r[filt[1][i]]=filt[2][i];
                message_part="".join(r)

                
            #return " ".join( [command[0],command[1],command[2],message_part] )
            return irc_protocol.command(command[0],
                                        command[1],
                                        command[2],message_part)
        else:
            return message

    ####################################
    ##
    ## 
    ##
    def load_filters(self, filename):
        try:
            f=open( filename )
        except Exception,e:
            self.log('***','cannot load filters'+e.__str__())
        else:
            while True:
                try:
                    filtr  = re.compile(f.readline()[:-1],re.IGNORECASE)
                    fields = eval(f.readline())
                    replace= eval(f.readline())
                except:
                    break #read error of some sort! (EOF?)
                else:
                    self.log_filters.append( (filtr,fields,replace) )
                

    ##################################
    ##
    def log( self, id, message ):
        now=time.localtime()
        #time stamp: yyyy/mm/dd hh:mm:ss.ss
        ts="%04d/%02d/%02d %02d:%02d:%05.02f" % (now[0:5]+(time.time() % 10,))
        message=self.filter(message)
        
        self.log_list.append((ts,id,message))
        print ts, id, message

        try:
            message=message.decode('utf-8')
        except:
            # something not-utf-8?
            try:
                message=message.decode('latin_1')
            except:
                message='<message contains unknown encodins>'
                
                
        message=message.encode('unicode_escape')
        
        
        try:
            self.log_file.write( ts+'\t'+id+'\t'+message+'\n' )
        except Exception,e:
            print "*** error writing to log:", e
        else:
            pass

    ##########################
    ##
    ## sends with echo
    ##
    ## also hooks with logger
    ##
    ## if message is a string, it is
    ## sent verbatim
    ##
    ## if message is a list of string,
    ## the list is joined (it is expected
    ## that the list is a command-list:
    ## (from,command,to,message) )
    ##
    ## Single messages are to be of 510 bytes+\r\n
    ## maximum.
    ##
    def send( self, message):

        if type(message) is list:
            s = " ".join([x for x in message if x!=""])
        else:
            s = message

        try:
            self.server_socket.send(s+"\r\n")
        except socket.error,e:
            self.log("***","send error on "+self.server_name+" : "+e.__str__())
#            self.error=e
            return False
        else:
            self.log('-->',s);
            return True


    ##########################
    ##
    ## to send several lines
    ## at once
    ##
    def send_multiline( self, script ):
        for line in script:
            self.send( line )
            time.sleep(0.100) # to avoid throttling server


    ##########################
    ##
    ## receives with echo
    ##
    ## also hooks with logger
    ##
    ## waits at least until the
    ## next \r\n (received buffer
    ## must end in \r\n)
    ##
    def recv(self):
        eol=False
        s="";

        while not eol:
            try:
                s+=self.server_socket.recv(4096)

            except socket.timeout, e:
                self.log( "***","recv error on "
                          +self.server_name+" : "+e.__str__())
                self.error = e
                return
            except socket.error,e:
                self.log("***","recv error on "
                         +self.server_name+" : "+e.__str__())
                self.error = e
                return
            else:
                # check if buffer ends with
                # \r\n
                #
                if s[-2:]=='\r\n':
                    eol=True

        lines=[x for x in s.split("\r\n") if x!=""]
        for line in lines:
            self.log("<--",line)

        # queues lines
        self.server_in_queue += lines


    ##########################
    ##
    ## Shoots the input
    ##
    def receive(self, peek=False):
        if self.server_in_queue==[]:
            self.recv()

        if peek==True:
            return self.server_in_queue
        else:
            t = self.server_in_queue
            self.server_in_queue=[]
            return t

    ##########################
    ##
    ## joins a channel
    def join( self,join_list):

        for channel in join_list:
            self.server_channel_list.add( channel )

        self.send("JOIN "+",".join(join_list));


    ##########################
    ##
    ## parts a channel
    def part( self, part_list):
        for channel in part_list:
            self.server_channel_list.remove(channel)

        self.send("PART "+",".join(part_list) )



    #########################
    ##
    ## opens log and reloads
    ## it to memory.
    ##
    def open_log( self,server_name ):
        try:
            #self.log_file = codecs.open(server_name+".log","a+",'utf-8');
            self.log_file = open(server_name+".log","a+");
        except:
            print "*** error, can't open",server_name,"log"
            self.log_file=None
            return False
        else:
            print "=== opened log "
            print "=== reloading log. this may take a while."
            for line in self.log_file.readlines():
                fields=line.split('\t')

                try:
                    fields[2]=fields[2].decode('unicode_escape')
                    fields[2]=fields[2].encode('utf-8')
                except:
                    pass
                
                fields=( fields[0],
                         fields[1],
                         fields[2][:-1]
                         )

                self.log_list.append( fields )
            return True

    #########################
    ##
    ## Closes log files and fluhes
    ## logs
    def close_log():

        if self.log_file is not None:
            self.log_file.close()

        self.log_list=None;


    #########################
    ##
    ## connects to a server and
    ## logs in
    ##
    ##
    def connect( self,
                 server_name,
                 port,
                 nick,
                 full_name):

        if self.connected:
            self.disconnect()

        self.server_name=server_name
        self.server_port=port
        self.server_nick=nick

        self.server_socket=socket.socket( socket.AF_INET,
                                          socket.SOCK_STREAM );
        try:
            self.server_socket.connect( (self.server_name,self.server_port) )
        except socket.herror,e:
            print "Lookup error on",self.server_name,":",e.__str__()
            self.server_error=e;
            return False
        except socket.error,e:
            print "cannot connect on",self.server_name,":",e.__str__()
            self.server_error=e;
            return False
        except socket.timeout,e:
            print "connection timeout on",self.server_name,":",e.__str__()
            self.server_error=e;
            return False

        else:
            self.open_log(self.server_name)
            time.sleep(2)
            retry=True
            self.send_multiline( ["USER"
                                         + " bakeneko_bot"
                                         + " 0 "
                                         + " *"
                                         + " :"+full_name,
                                         "NICK "+nick ]);
            while retry:
                # Here, the server is waiting for a response to
                # our identd server.
                # If we have no identd server, the old code would
                # change our nick and re-register...
                # -- pireau
                time.sleep(2)

                # here, we are logging in, and
                # we are not interested in
                # processig anything but the
                # login message 001
                #
                lines=self.receive()

                for line in lines:
                    parsed=irc_protocol.parse(line);
                    if parsed[1]=="001":
                        # success! RPL_WELCOME
                        retry=False
                else:
                    nick=nick+"_" # fix-me!
                    #
                    # eventually, we should be
                    # able to recover the nick
                    # name (should be implemented
                    # as a plugin; not here )

            self.server_nick = nick # if changed during loging!
            self.connected=True
            return True


    #########################
    ##
    ## disconnects gracefully
    ##
    def disconnect(self,quit_message=""):

        success=False
        try:
            self.send("QUIT :"+quit_message)
        except:
            self.log("***","Quit error?")
        else:
            time.sleep(2) # some timeout

            # here, we are quitting, so it
            # doesn't matter if we eat the
            # input queue, commands aren't
            # going to be honored anyway
            #
            lines=self.receive()

            for line in lines:
                if line[0:5]=="ERROR":
                    success=True

        if not success:
            self.log("***","slow quit, forcing")

        self.server_socket.close()
        self.connected=False
        if self.log_file is not None:
            self.log_file.close()
            self.log_file=None
        self.log_filters=[]

        self.server_name=None
        self.server_socket=None
        self.server_error=None        
        self.server_port=None
        self.server_nick=None
        self.server_channel_list=None
        self.server_in_queue=[]
        
        return True


    ##########################
    ##
    def __init__(self):
        self.server_name=None
        self.server_socket=None
        self.server_error=None        
        self.server_ort=None
        self.server_nick=None
        self.server_channel_list=set()
        self.server_in_queue=[]
        self.error=False
        self.connected=False
        self.log_file=None
        self.log_list=[]
        self.log_filters=[]



    ##########################
    ##
    ##
    def __del__(self):
        if self.connected:
            self.disconnect()
